import { Observable, Observer, filter, map } from 'rxjs';
import { ReactiveProperty } from './ReactiveProperty';
import { _decorator, tween, __private, ITweenOption, log, director, Color, isValid, color, Node } from 'cc';
import { Vec3 } from 'cc';
import { v3 } from 'cc';
import { deBoorcox, linear } from '../../utils/math';
import { easing } from 'cc';
const { ccclass, property } = _decorator;

interface HasColor {
    color: Color; // 或其他适当的类型，如 Color 类型
}
// 因为color直接用tween {color: xxxx}, 将会产生闪烁 
// https://forum.cocos.org/t/topic/132585/7
// 在此封装为同时对rgb进行tween
export function TweenToStartColor<T extends HasColor>(target: T, duration: number, toColor: Color, opts?: ITweenOption) {
    const c0 = color(target.color);
    return TweenToStart(c0, duration, { r: toColor.r, g: toColor.g, b: toColor.b, a: toColor.a }, opts).pipe(map(__ => {
        if (__?.status == 'update') {
            if (isValid(target)) {
                target.color = c0;
            }
        }
    }));
}

export function TweenToStart<T>(target: T, duration: number, props?: __private._cocos_tween_tween__ConstructorType<unknown>, opts?: ITweenOption)
    : Observable<{ target: T, ratio?: number, status: string }> {
    return _TweenToStart(target, duration, props, opts).observable;
}

// 可随时中断的Tween
export function TweenToStartWithSkip<T>(target: T, duration: number, props?: __private._cocos_tween_tween__ConstructorType<unknown>, opts?: ITweenOption)
    : { observable: Observable<{ target: T, ratio?: number, status: string }>, skip: () => void } {
    return _TweenToStart(target, duration, props, opts);
}

// 封装了一个极简的帧序列
export function TweenSimpleWithSkip(duration: number) {
    let { observable, skip } = _TweenToStart(director.getScene(), duration)
    return { observable: observable.pipe(filter(_ => _.status == 'update'), map(_ => _.ratio)), skip };
}

function _TweenToStart<T>(target: T, duration: number, props?: __private._cocos_tween_tween__ConstructorType<unknown>, opts?: ITweenOption): { observable: Observable<any>, skip: () => void } {
    let needSkip = ReactiveProperty.Create(false);
    let skip = () => needSkip.value = true;
    const observable = new Observable((observer: Observer<{ target: T, ratio?: number, status: string }>) => {
        if (opts == null) opts = {};
        opts.onStart = (target: unknown) => observer.next({ target: <T>target, status: 'start' });
        opts.onUpdate = (target: unknown, ratio: number) => observer.next({ target: <T>target, ratio, status: 'update' });
        opts.onComplete = (target: unknown) => {
            observer.next({ target: <T>target, status: 'complete' });
            observer.complete();
        }
        let _t = needSkip.value ? null : tween(target as any).to(duration, props, opts).start();
        needSkip.subscribeWhenTrue(() => {
            observer.next({ target: <T>target, status: 'skip' });
            observer.complete();
        });
        return () => {
            _t && _t.stop();
        }
    });
    return { observable, skip };
}

export function JumpTo_3D(target: Node, duration: number, targetPosition: Vec3, height: number, opts?: ITweenOption) {
    target.attr({ jump_t: 0, jump_start_pos: v3(target.position) });
    let newPosition = v3();
    let newPositionWithY = v3();
    return _TweenToStart(target, duration, { jump_t: 1 }, opts).observable.pipe(filter(_ => _.status == 'update'), map(_ => {
        let t = target['jump_t'];
        let jump_start_pos = target['jump_start_pos'];
        Vec3.lerp(newPosition, jump_start_pos, targetPosition, t);
        newPositionWithY.set(newPosition);
        if (t < .5) {
            let tn = linear(0, .5, 0, 1, t);
            tn = easing.quadOut(tn);
            newPositionWithY.y += linear(0, 1, 0, height, tn);
        } else {
            let tn = linear(.5, 1, 0, 1, t);
            tn = easing.quadIn(tn);
            newPositionWithY.y += linear(0, 1, height, 0, tn);
        }
        target.setPosition(newPositionWithY);
    }));
}

export function JumpTo_2D(target: Node, duration: number, targetPosition: Vec3, height: number, opts?: ITweenOption) {
    target.attr({ jump_t: 0, jump_start_pos: v3(target.position) });
    let newPosition = v3();
    let newPositionWithY = v3();
    return _TweenToStart(target, duration, { jump_t: 1 }, opts).observable.pipe(filter(_ => _.status == 'update'), map(_ => {
        let t = target['jump_t'];
        let jump_start_pos = target['jump_start_pos'];
        Vec3.lerp(newPosition, jump_start_pos, targetPosition, t);
        newPositionWithY.set(newPosition);
        if (t < .5) {
            let tn = linear(0, .5, 0, 1, t);
            tn = easing.quadOut(tn);
            newPositionWithY.y += linear(0, 1, 0, height, tn);
        } else {
            let tn = linear(.5, 1, 0, 1, t);
            tn = easing.quadIn(tn);
            newPositionWithY.y += linear(0, 1, height, 0, tn);
        }
        target.setPosition(newPositionWithY);
    }));
}

export function TweenToStartBSpine(node: Node, duration: number, pList: Vec3[], k?: number, opts?: ITweenOption) {
    k = k ?? 3; // 默认3次B样条
    node.attr({ _t: 0 });
    let alg = new deBoorcox(pList, k, true);
    return TweenToStart(node, duration, { _t: 1 }, opts).pipe(map(__ => {
        if (__?.status == 'start') {
            let u = k;
            node.setPosition(alg.solve(u));
        } else if (__?.status == 'update') {
            let u = linear(0, 1, k, (pList.length + k - 3) + k, __.ratio);
            node.setPosition(alg.solve(u));
        } else if (__?.status == 'complete') {
        }
        return __;
    }));
}

export function MoveByPointList(node: Node, duration: number, pList: Vec3[], opts?: ITweenOption) {
    let lengthList = [{ len: 0, sum: 0 }];
    // let totalLength = 0;
    for (let i = 1; i < pList.length; i++) {
        let p = pList[i];
        let pp = pList[i - 1];
        let len = Vec3.distance(p, pp);
        let sum = lengthList[i - 1].sum + len;
        lengthList.push({ len, sum });
    }
    let totalLength = lengthList[lengthList.length - 1].sum;
    node.attr({ _t: 0 });
    return TweenToStart(node, duration, { _t: 1 }, opts).pipe(map(__ => {
        if (__?.status == 'start') {
            node.setPosition(pList[0]);
        } else if (__?.status == 'update') {
            let ratio = __.ratio;
            for (let i = 1; i < pList.length; i++) {
                if (ratio <= lengthList[i].sum / totalLength) {
                    let p0Sum = lengthList[i - 1].sum;
                    let p1Sum = lengthList[i].sum;
                    let curSum = totalLength * ratio;
                    let t = (curSum - p0Sum) / (p1Sum - p0Sum);
                    let position = pList[i - 1].lerp(pList[i], t);
                    node.setPosition(position);
                    break;
                }
            }
        } else if (__?.status == 'complete') {
            node.setPosition(pList[pList.length - 1]);
        }
        return __;
    }))

}